iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0

Introduction

接下來,我們需要考慮到 狀態(state) 這個概念。在 Streamlit 中,許多之後要時做的 Component(例如Button、Textbox)都需要狀態資訊。

State

State 的主要功能是將 Component 的 ID 與其對應的值進行關聯。由於不同元件可能儲存不同類型的值,因此我們將值的型別設定為 any。

為了確保多個執行緒同時存取 State 時不會發生數據衝突,我們使用了 sync.RWMutex 來保護 State。

Thread Safe 是一個複雜的問題。即使使用了 RWMutex,仍然可能存在一個重要的問題:Script 可能會修改 State 中的值,這可能導致預期外的行為。例如,一個腳本可能會不小心覆蓋了另一個腳本設定的值。

這些問題需要我們在 Service 使用 State 時仔細考慮。我們先將這些問題保留下來,後面再深入探討解決方案。

package tgstate

import "sync"

type State struct {
	data map[string]any
	lock sync.RWMutex
}

func NewState() *State {
	return &State{
		data: map[string]any{},
	}
}

func (s *State) Set(key string, val any) {
	s.lock.Lock()
	defer s.lock.Unlock()
	s.data[key] = val
}

func (s *State) Del(key string) {
	s.lock.Lock()
	defer s.lock.Unlock()
	delete(s.data, key)
}

func (s *State) Get(key string) any {
	s.lock.RLock()
	defer s.lock.RUnlock()
	return s.data[key]
}

func (s *State) GetString(key string) string {
	v := s.Get(key)
	if v == nil {
		return ""
	} else {
		return v.(string)
	}
}

func (s *State) GetBool(key string) bool {
	v := s.Get(key)
	if v == nil {
		return false
	} else {
		return v.(bool)
	}
}

State Table

State Table 用於管理多個 State。除了維護 State 與其 ID 之間的對應關係外,它還提供了一種垃圾回收機制。我們採用生存時間(TTL)的方式,定期清理那些長時間未被使用的 State。

垃圾回收機制:

  • 每隔一段時間,會有一個 Tick trigger,掃描 State Table 中的所有 State。
  • 如果某個 State 的最後一次存取時間超過了設定的 TTL,則將其刪除。
package tgstate

import (
	"sync"
	"time"
	"github.com/google/uuid"
)

type stateData struct {
	state     *State
	timestamp time.Time
}

type StateTable struct {
	table map[string]*stateData
	lock  sync.RWMutex

	wg   sync.WaitGroup
	stop chan bool

	stateTTL   time.Duration
	clearCycle time.Duration
}

func NewStateTable(stateTTL, clearCycle time.Duration) *StateTable {
	return &StateTable{
		table: map[string]*stateData{},
		stop:  make(chan bool),

		stateTTL:   stateTTL,
		clearCycle: clearCycle,
	}
}

func (st *StateTable) Start() {
	st.wg.Add(1)
	go func() {
		defer st.wg.Done()
		ticker := time.NewTicker(st.clearCycle)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				st.gc()
			case <-st.stop:
				return
			}
		}
	}()
}

func (st *StateTable) Stop() {
	st.stop <- true
	st.wg.Wait()
}

func (st *StateTable) gc() {
	st.lock.Lock()
	defer st.lock.Unlock()
	rmKeys := []string{}
	for key, data := range st.table {
		if time.Since(data.timestamp) >= st.stateTTL {
			rmKeys = append(rmKeys, key)
		}
	}

	for _, key := range rmKeys {
		delete(st.table, key)
	}
}

func (st *StateTable) Get(id string) *State {
	st.lock.RLock()
	defer st.lock.RUnlock()
	d := st.table[id]
	if d == nil {
		return nil
	}
	d.timestamp = time.Now()
	return d.state
}

func (st *StateTable) NewState() (*State, string) {
	st.lock.Lock()
	defer st.lock.Unlock()
	var newID string
	for {
		newID := uuid.New().String()
		if st.table[newID] == nil {
			break
		}
	}
	state := NewState()
	st.table[newID] = &stateData{
		state:     state,
		timestamp: time.Now(),
	}
	return state, newID
}

上一篇
Day7 Project Layout
下一篇
Day9 溝通 Button 狀態
系列文
用 Golang 實作 streamlit 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言